/*
simulator.c

Author: Jesse Niekraszewicz
Date: April 5, 2012

For: SYSC 4907 Engineering Project

Description: 
sends a "migration message" to the Migration Manager (migrationManager.c)

To Perform a Migration:
after the program has started enter the IP address of the machine the the "idle" Migration Simulator
is running on into the console window that the "running" Migration Simulator is running on

Arguments:  1) clientIP [string] -> the IP address of the machine that the Migration Manager (migrationManager.c) is running on
			2) isRunning [string] -> [true or false] should this instance start as running or idle
*/
#include <stdlib.h>
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <stdbool.h>
#include <arpa/inet.h>
#include <string.h>
#include <errno.h>


#define MIG_PORT 8886 
#define COM_PORT 8887
#define MIG_MAN_PORT 8888
#define BUFFSIZE 1024


int isRunning = 0; //true when this program is communicating with the server
				//false when the other program is communicating with the server
				
int counter = 0; //the counter that will be sent to the client

void *clientIP; //the IP address of the client

//Callback function for migration send thread
void *m_sThread(void *arg){
	while(1){
		if (isRunning){
			int serverSocket, migManSocket;
			int *serverData;
			char *migManData;
			struct sockaddr_in serverAddr, migManAddr;
			void *validIpAddress;
			char ipAddress[INET_ADDRSTRLEN];
			char oldIP[INET_ADDRSTRLEN];
			char migManAddress[INET_ADDRSTRLEN];
			
			validIpAddress = malloc(sizeof(struct in_addr)); //the valid IP address of the server to migrate to

			printf("MIG_SEND thread running\n");
			
			//create a TCP socket for the other server 
			serverSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
			if (serverSocket < 0){
				printf("ERROR: could not create MIG_SEND socket\n");
				exit(1);
			}
			
			//create a TCP socket for the migration manager 
			migManSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
			if (serverSocket < 0){
				printf("ERROR: could not create MIG_SEND socket\n");
				exit(1);
			}
			
			printf("created MIG_SEND socket\n");
			while (isRunning){
				printf("\n Enter the IP address of the machine to migrate to:\n");

				//waits for user to enter the IP address of the machine to migrate to
				//i.e. 192.168.1.1
				gets(ipAddress);
				
				//copies ipAddress to validIpAddress - returns 1 on success
				if (inet_pton(AF_INET, ipAddress, &validIpAddress)){
					
					serverAddr.sin_family = AF_INET;
					serverAddr.sin_port = htons(MIG_PORT); //the port to bind to
					serverAddr.sin_addr.s_addr = (int) validIpAddress; // the IP Address to bind to
					bzero(&(serverAddr.sin_zero),8); //set all bits to 0
				
					migManAddr.sin_family = AF_INET;
					migManAddr.sin_port = htons(MIG_MAN_PORT); //the port to bind toc
					migManAddr.sin_addr.s_addr = (int) clientIP; // the IP Address to bind to
					bzero(&(migManAddr.sin_zero),8); //set all bits to 0
					
					//connect to the other server's socket
					printf("Connecting to other server instance at %s\n", ipAddress);
					if (connect(serverSocket, (struct sockaddr *) &serverAddr, sizeof(serverAddr)) < 0){
						printf("ERROR: could not connect to MIG_SEND socket %i\n",MIG_PORT);
						printf( "Error : %s\n", strerror( errno ));
					}
					
					//turn migration manager's IP into a string
					inet_ntop(AF_INET, &clientIP, migManAddress, INET_ADDRSTRLEN);
					printf("Connecting to migration manager at %s\n", migManAddress);
					//connect to the other migration manager's socket
					if (connect(migManSocket, (struct sockaddr *) &migManAddr, sizeof(migManAddr)) < 0){
						printf("ERROR: could not connect to MIG_SEND socket %i\n",MIG_MAN_PORT);
						printf( "Error : %s\n", strerror( errno ));
					}
					
					//send the migration message to the other server instance
					printf("sending migration message to other server instance: %i\n",(counter));
					serverData = (int *)(counter);
					if(send(serverSocket, &serverData, BUFFSIZE, 0) != BUFFSIZE){
						printf("ERROR: Failed to send migration message to other server instance\n");
						printf( "Error : %s\n", strerror( errno ));
					}
					
					//save the IP of the machine to migrate to as the data to be sent to the migration manager
					migManData = malloc(strlen(ipAddress));
					migManData = ipAddress;
				
					//send the migration message to the migration manager
					printf("migrating to IP: %s\n",ipAddress);
					if(send(migManSocket, migManData, INET_ADDRSTRLEN, 0) != INET_ADDRSTRLEN){
						printf("ERROR: Failed to send migration message to migration manager\n");
						printf( "Error : %s\n", strerror( errno ));
					}
					
					//pass control to the other server
					isRunning = false;
				}else{
					printf("Please enter a valid IP address\n");
				}
			}
			//close the socket
			close(serverSocket);
		}
		sleep(1);
	}
}


//Callback function for migration receive thread
void *m_rThread(void *arg){
	while(1){
		sleep(1);
		if (!isRunning){
			int serverSocket, clientSocket;
			int *data;
			struct sockaddr_in myAddr, serverAddr;
			int bytesReceived;
			socklen_t sockLength;
			char optval;
			
			printf("MIG_RECV thread running\n");
			//create a TCP socket 
			serverSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
			if (serverSocket < 0){
				printf("ERROR: could not create MIG_RECV socket\n");
				exit(1);
			}
			//allow the socket to be used immediatly affter being closed
			optval = 1;
			if (setsockopt(serverSocket, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(serverAddr)) < 0){
				printf("ERROR: could not reuse MIG_RECV socket\n");
				exit(1);
			}

			myAddr.sin_family = AF_INET;
			myAddr.sin_port = htons(MIG_PORT); //the port to bind to
			myAddr.sin_addr.s_addr = INADDR_ANY; // the IP Address to bind to
			bzero(&(myAddr.sin_zero),8); //set all bits to 0

			//bind to the socket
			if (bind(serverSocket, (struct sockaddr *) &myAddr, sizeof(myAddr)) < 0){
				printf("ERROR: could not bind to MIG_RECV socket %i\n",MIG_PORT);
				printf( "Error : %s\n", strerror( errno ));
				exit(1);
			}	

			//listen on socket
			if (listen(serverSocket, 1) < 0){
				printf("ERROR: failed to listen on MIG_RECV socket %i\n",MIG_PORT);
				exit(1);
			}

			while(!isRunning){
				sockLength = sizeof(serverAddr);
				printf("Waiting for connection\n");
				clientSocket = accept(serverSocket, (struct sockaddr *) &serverAddr, &sockLength);
				if (clientSocket < 0){
					printf("ERROR: Failed to accept client connection\n");
				}

				//recieve message from the other server
				bytesReceived = recv(clientSocket, &data, BUFFSIZE, 0);
				if (bytesReceived < 0){
					printf("ERROR: Failed to recieve MIG_RECV message\n");
					printf( "Error : %s\n", strerror( errno ));
				}else{
					//display the message received
					printf("Received message: %i\n",(int)data);
					counter=(int)data;
					printf("MIGRATED TO THIS SERVER\n");
					isRunning = true;		
				}
				close(clientSocket);
				close(serverSocket);
				sleep(1);
			}	
		}	
	}
}


//Callback function for communication thread
void *cThread(void *arg){
	while(1){
	sleep(10); //delay for demo to allow time for set up
		if (isRunning){	
			int serverSocket;
			int *send_data;
			struct sockaddr_in serverAddr;
			int bytesSent;
			struct timeval start, end, current;
			double mtime;
			long seconds, useconds;
			int numPackets;
			
			send_data = malloc(BUFFSIZE);
			
			printf("COM thread running\n");
			//create a UDP socket 
			serverSocket = socket(AF_INET, SOCK_DGRAM, 0);
			if (serverSocket < 0){
				printf("ERROR: could not create COM socket\n");
				exit(1);
			}

			serverAddr.sin_family = AF_INET;
			serverAddr.sin_port = htons(COM_PORT); //the port of the client
			serverAddr.sin_addr.s_addr = (int) clientIP; // the IP Address to send to
			bzero(&(serverAddr.sin_zero),8); //set all bits to 0
		 
			gettimeofday(&start,NULL);
			while(isRunning){	
				//set the counter as the data to be sent
				send_data = (int *)counter;	
				//printf("%i\n",(int)send_data);

				//send data to client
				bytesSent = sendto(serverSocket, &send_data, BUFFSIZE, 0, (struct sockaddr *)&serverAddr, sizeof(struct sockaddr));

				if (bytesSent < 0){
					printf("ERROR: Failed to send COM message\n");
				}
				numPackets++;
				gettimeofday(&current, NULL);
				if ((current.tv_sec - start.tv_sec)==60){
					printf("Sent %i -> %lf packets/sec\n",counter,(numPackets/60.0));
					gettimeofday(&start,NULL);
					numPackets = 0;
				}
				/*if (counter%100000 == 0){
					gettimeofday(&end,NULL);
					seconds = end.tv_sec - start.tv_sec;
					useconds = end.tv_usec - start.tv_usec;
					mtime = (seconds + (useconds/1000000));
					printf("Sent %i -> %lf packets/sec\n",counter,(100000/mtime));
					gettimeofday(&start,NULL);				
				}*/

				//increment the counter
				counter = counter+1;
				//wait 100ms - send 10 messages/sec
				usleep(300000);
			}
			close(serverSocket);
		}
	sleep(1);
	}
}


//main function that initiates the 3 threads
int main(int argc, char *argv[]){
	pthread_t migrationSendThread, migrationRecvThread, communicationThread;
	int m_sT_status, m_rT_status, cT_status;
	printf("?\n");
    if (argc != 3) { //1st param is always name of program
       printf("requires 2 arg: 1) client's IP   2) (bool) instance start as running?\n");
       exit(0);
    }
    printf("starting simulator \n");
    
    clientIP = malloc(sizeof(struct in_addr)); //the IP address of the client
	
  	//copies argv[1] to clientIP - returns 1 on success
	if (!inet_pton(AF_INET, &*argv[1], &clientIP)){  
		printf("Invalid client IP address\n");
		exit(1);
	}
	
	//determine if this instance should start of running or idle
	if (strcmp(&*argv[2], "true")==0){
		isRunning = 1;
		printf("this instance is starting as 'running'\n");
	}else{
		printf("this instance is starting as 'idle'\n");
	}
	
	printf("using %s as the client IP address\n",&*argv[1]);
	
	printf("starting Migration SEND thread\n");
	//initialize migrationSendThread (handles sending migration command to other server)
	m_sT_status = pthread_create(&migrationSendThread, NULL, m_sThread, NULL);
	sleep(1);
	printf("starting Migration RECV thread\n");
	//initialize thread 1 (handles recieving migration command from other server)
	m_rT_status = pthread_create(&migrationRecvThread, NULL, m_rThread, NULL);
	sleep(1);
	printf("starting Communication thread\n");
	//initialize thread 2 (handles sending messages to client)
	cT_status = pthread_create(&communicationThread, NULL, cThread, NULL);
	sleep(1);
	
	pthread_join(migrationSendThread, NULL);
	pthread_join(migrationRecvThread, NULL);
	pthread_join(communicationThread, NULL);

	return 0;
}
